package zdream.pmfield.storage;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

import java.io.InputStream;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import zdream.pmfield.core.PLogger;
import zdream.pmfield.core.Result;
import zdream.pmfield.data.foe.*;

/**
 * @author Zdream
 * @date 2023-11-10
 * @since 0.0.1
 */
public class MovesJsonLoader extends JsonLoader {
	public static List<MoveTemplate> loadFromPath(Path path) {
		final JsonArray array = fromPath(path).getAsJsonArray();
		return loadFromJsonArray(array);
	}

	public static List<MoveTemplate> loadFromInputStream(InputStream stream) {
		final JsonArray array = fromInputStream(stream).getAsJsonArray();
		return loadFromJsonArray(array);
	}

	public static List<MoveTemplate> loadFromJsonArray(JsonArray array) {
		return array.asList().stream()
				.map(JsonElement::getAsJsonObject)
				.map(MovesJsonLoader::loadOne)
				.filter(Objects::nonNull)
				.collect(Collectors.toList());
	}

	public static MoveTemplate loadOne(JsonObject object) {
		final int id = object.get("id").getAsInt();
		MoveTemplate template = new MoveTemplate(id);

		template.name = object.get("name").getAsString();
		template.level = object.get("lv").getAsInt();

		JsonElement element = object.get("type");
		if (element == null || !element.isJsonArray()) {
			PLogger.warn("MovesJsonLoader", "Move Json #%d: Invalid type", id);
			return null;
		}
		final List<JsonElement> typeArray = element.getAsJsonArray().asList();
		if (typeArray.size() == 1) {
			template.types = new Type.TypeWithWeight[] {
					new Type.TypeWithWeight(Type.valueOf(typeArray.get(0).getAsString()))
			};
		} else {
			PLogger.warn("MovesJsonLoader", "Move Json #%d: 多个 type 还没做", id);
			return null;
		}

		element = object.get("using");
		if (element == null || !element.isJsonPrimitive() || element.getAsString().length() > 2) {
			PLogger.warn("MovesJsonLoader", "Foe Json #%d: Invalid using", id);
			return null;
		}
		final MoveType[] moveTypes = parseMoveType(element.getAsString());
		template.attackerUsing = moveTypes[0];
		template.defenseUsing = moveTypes[1];

		// tags
		element = object.get("tag");
		if (element != null) {
			template.tags = parseTags(element);
		}

		if (template.hasTags("charging")) {
			final Result result = parseChargingMoves(object, template);
			if (!result.success) {
				PLogger.warn("MovesJsonLoader", "Foe Json #%d: %s", id, result.errorMessage);
				return null;
			}
		} else {
			final Result result = parseVStatAndPairs(object, template);
			if (!result.success) {
				PLogger.warn("MovesJsonLoader", "Foe Json #%d: %s", id, result.errorMessage);
				return null;
			}
		}

		// cost
		element = object.get("cost");
		if (element != null) {
			int[] cost = parseCost(element.getAsString());
			template.movePoint = (byte) cost[0];
			template.actionPoint = (byte) cost[1];
		} else {
			template.movePoint = 0;
			template.actionPoint = 1;
		}

		// target
		element = object.get("t");
		if (element != null) {
			template.target = parseMoveTarget(element.getAsString());
		} else {
			template.target = parseMoveTarget(null);
		}

		// additions
		element = object.get("a");
		if (element != null) {
			template.additions = parseAdditionalEffects(element);
		}

		return template;
	}

	private static Result parseVStatAndPairs(JsonObject object, MoveTemplate template) {
		JsonElement element;

		// vStats
		template.vStats = parseVStats(object.get("v"));
		if (template.vStats == null) {
			return Result.ofFailure("Invalid vStats");
		}

		// pairs
		element = object.get("pairs");
		if (element == null || !element.isJsonArray() || element.getAsJsonArray().size() == 0) {
			return Result.ofFailure("Invalid Pairs");
		}
		List<MoveCheckPair> pairs = parsePairs(element.getAsJsonArray());
		template.pairs = pairs.toArray(new MoveCheckPair[0]);

		// checkRateFixed
		element = object.get("rateFix");
		if (element != null) {
			template.checkRateFixed = element.getAsInt();
		}

		return Result.ofSuccess();
	}

	private static MoveType[] parseMoveType(String text) {
		MoveType[] results = new MoveType[2];

		for (int i = 0; i < text.length(); i++) {
			char ch = text.charAt(i);

			switch (ch) {
			case 'P': results[i] = MoveType.PHYSICAL; break;
			case 'S': results[i] = MoveType.SPECIAL; break;
			case 's': results[i] = MoveType.STATUS; break;
			case 'M': results[i] = MoveType.MOVEMENT; break;
			default: throw new IllegalArgumentException("invalid move type: " + text);
			}
		}

		if (text.length() == 1) {
			results[1] = results[0];
		}
		return results;

	}

	private static VStat parseVStat(char ch) {
		switch (ch) {
		case 'S': return VStat.STRENGTH;
		case 'V': return VStat.VITALITY;
		case 'I': return VStat.INTELLIGENCE;
		case 'A': return VStat.AWARENESS;
		case 'T': return VStat.TALENT;
		case 'F': return VStat.FLEXIBILITY;
		default: return VStat.LUCK;
		}
	}

	private static List<MoveCheckPair> parsePairs(JsonArray array) {
		List<MoveCheckPair> results = new ArrayList<>(array.size());
		for (JsonElement element : array) {
			JsonObject object = element.getAsJsonObject();
			MoveCheckPair pair = new MoveCheckPair();

			JsonPrimitive lv = object.get("lv").getAsJsonPrimitive();
			if (lv.isString() && lv.getAsString().equals("+")) {
				pair.level = MoveCheckPair.LEVEL_MORE;
				pair.result = MoveCheckPair.RESULT_MAJOR_SUCCESS;
			} else {
				pair.level = object.get("lv").getAsByte();
				pair.result = parsePairResult(object.get("r").getAsString());
			}

			if (object.has("p")) {
				pair.power = object.get("p").getAsInt();
			}
			results.add(pair);

			if (object.has("a")) {
				pair.additions = parseAdditionalEffects(object.get("a"));
			}
		}
		return results;
	}

	private static byte parsePairResult(String text) {
		switch (text) {
		case "MS": return MoveCheckPair.RESULT_MAJOR_SUCCESS;
		case "S": return MoveCheckPair.RESULT_SUCCESS;
		case "F": return MoveCheckPair.RESULT_FAILURE;
		case "MF": default: return MoveCheckPair.RESULT_MAJOR_FAILURE;
		}
	}

	static String[] parseAdditionalEffects(JsonElement effectsElements) {
		List<String> effects;
		if (effectsElements.isJsonArray()) {
			final JsonArray array = effectsElements.getAsJsonArray();
			effects = new ArrayList<>(array.size());
			effects.addAll(array.asList().stream()
					.map(JsonElement::getAsString)
					.collect(Collectors.toList()));
		} else {
			effects = new ArrayList<>(1);
			effects.add(effectsElements.getAsString());
		}

		effects.removeIf(text -> !MoveAdditionalEffects.validate(text));
		if (effects.isEmpty()) {
			return null;
		}
		return effects.toArray(new String[0]);
	}

	/**
	 * @return [movePoints, actionPoints]
	 */
	private static int[] parseCost(String text) {
		int[] cost = new int[2];
		switch (text) {
		case "": return cost;
		case "A+": cost[1] = MoveTemplate.COST_ALL; return cost;
		case "M+": cost[0] = MoveTemplate.COST_ALL; return cost;
		case "AM+": cost[0] = cost[1] = MoveTemplate.COST_ALL; return cost;
		}

		for (char c : text.toCharArray()) {
			switch (c) {
			case 'M': cost[0] ++; break;
			case 'A': cost[1] ++; break;
			default: throw new IllegalArgumentException("invalid move cost: " + text);
			}
		}
		return cost;
	}

	private static MoveTarget parseMoveTarget(String text) {
		if (text == null) {
			return new MoveTarget(MoveTargetType.ENEMY, 0, 1, 2, 3);
		}

		final String[] split = text.split(":");
		MoveTargetType type;
		// range 必须从小到大排列
		int[] range;

		switch (split[0]) {
		case "single": case "":
			type = MoveTargetType.ENEMY;
			range = new int[] {0, 1, 2, 3};
			break;
		case "self":
			type = MoveTargetType.SELF;
			range = new int[] {0};
			break;
		case "seat":
			type = MoveTargetType.SEAT;
			range = new int[] {0, 1, 2, 3};
			break;
		default: throw new IllegalArgumentException("invalid move target: " + text);
		}

		if (split.length == 1) {
			return new MoveTarget(type, range);
		}

		final String rangeText = split[1].trim();
		if (rangeText.length() != 0) {
			Set<Integer> ints = new HashSet<>();
			for (String rangeItem : rangeText.split(",")) {
				final String[] pair = rangeItem.split("-");
				if (pair.length > 2) {
					throw new IllegalArgumentException("invalid move range: " + rangeItem);
				}

				if (pair.length == 1) {
					int rangeInt = Integer.parseInt(pair[0]);
					if (rangeInt > 10 || rangeInt < 0) {
						throw new IllegalArgumentException("invalid move range: " + rangeItem);
					}
					ints.add(rangeInt);
					continue;
				}

				int from = Integer.parseInt(pair[0]);
				int to = Integer.parseInt(pair[1]);
				if (from > to || to >= 10 || from < 0) {
					throw new IllegalArgumentException("invalid move range: " + rangeItem);
				}
				for (int i = from; i <= to; i++) {
					ints.add(i);
				}
			}

			range = ints.stream()
					.mapToInt(i -> i)
					.sorted()
					.toArray();
		}

		if (split.length == 2) {
			return new MoveTarget(type, range);
		}

		final String routeText = split[2].trim().toLowerCase();
		MoveRouteType route;
		switch (routeText) {
		case "t": case "throw":
			route = MoveRouteType.THROW; break;
		case "i": case "infiltrator":
			route = MoveRouteType.INFILTRATOR; break;
		case "b": case "beam":
			route = MoveRouteType.BEAM; break;
		case "f": case "field":
			route = MoveRouteType.FIELD; break;
		case "": case "d": case "direct":
			route = MoveRouteType.DIRECT; break;
		default:
			throw new RuntimeException("invalid move route type: " + routeText);
		}

		MoveBlastType blast = (split.length == 3) ? MoveBlastType.SINGLE : MoveBlastType.of(split[3].trim());
		return new MoveTarget(type, route, blast, range);
	}

	private static String[] parseTags(JsonElement element) {
		if (element.isJsonPrimitive()) {
			return new String[] {element.getAsString()};
		}
		final JsonArray array = element.getAsJsonArray();
		return array.asList().stream()
				.map(JsonElement::getAsString)
				.toArray(String[]::new);
	}

	private static VStat[] parseVStats(JsonElement element) {
		if (element == null || !element.isJsonPrimitive()) {
			return null;
		}
		VStat[] vStats = new VStat[element.getAsString().length()];
		int index = 0;
		for (char ch : element.getAsString().toCharArray()) {
			vStats[index++] = parseVStat(ch);
		}
		return vStats;
	}

	/* ****************
	 * Charging Moves *
	 **************** */

	private static Result parseChargingMoves(JsonObject object, MoveTemplate move) {
		final MoveTemplate.SubMove charging = new MoveTemplate.SubMove(MoveTemplate.SUB_TITLE_CHARGING);
		Result result = parseSubMove(object.get(charging.title).getAsJsonObject(), charging);
		if (!result.success) {
			return result;
		}

		final MoveTemplate.SubMove release = new MoveTemplate.SubMove(MoveTemplate.SUB_TITLE_RELEASE);
		result = parseSubMove(object.get(release.title).getAsJsonObject(), release);
		if (!result.success) {
			return result;
		}

		move.subs = new MoveTemplate.SubMove[] {charging, release};

		move.vStats = release.vStats;
		move.pairs = release.pairs;
		move.checkRateFixed = release.checkRateFixed;

		return Result.ofSuccess();
	}

	private static Result parseSubMove(JsonObject object, MoveTemplate.SubMove sub) {
		JsonElement element;

		// vStats
		sub.vStats = parseVStats(object.get("v"));
		if (sub.vStats == null) {
			throw new IllegalArgumentException("Invalid vStats in sub move: " + sub.title);
		}

		// pairs
		element = object.get("pairs");
		if (element == null || !element.isJsonArray() || element.getAsJsonArray().size() == 0) {
			return Result.ofFailure("Invalid Pairs");
		}
		List<MoveCheckPair> pairs = parsePairs(element.getAsJsonArray());
		sub.pairs = pairs.toArray(new MoveCheckPair[0]);

		// checkRateFixed
		element = object.get("rateFix");
		if (element != null) {
			sub.checkRateFixed = element.getAsInt();
		}

		// additions
		element = object.get("a");
		if (element != null) {
			sub.additions = parseAdditionalEffects(element);
		}

		return Result.ofSuccess();
	}
}
